//
// Created by yyancy on 2019/11/15.
//
#include <vector>
#include "calculator.h"


using namespace std;


void error(string string1);

void error(string string1) {
    cerr << string1 << "\n";
    exit(1);
}

vector<Variable> var_table;

double get_value(string s) {
    for (const Variable &v:var_table)
        if (v.name == s)return v.value;
    error("get:undefined variable" + s);
}

void set_value(string s, double d) {
    for (Variable &v: var_table) {
        if (v.name == s) {
            v.value = d;
            return;
        }
    }
    error("set:undefined variable" + s);
}




void Token_stream::putback(Token token) {
    if (full)perror("putback() into a full buffer");
    buffer = token;
    full = true;
}

void Token_stream::ignore(char c) {
    if (full && c == buffer.kind) {
        full = false;
        return;
    }
    full = false;
    char ch = 0;
    while (cin >> ch) {
        if (ch == c)return;
    }
}


Token_stream ts;

Token Token_stream::get() {
    if (full) {
        full = false;
        return buffer;
    }
    char ch;
    cin >> ch; // 会跳过空白(空格,新行,制表符等)
    switch (ch) {
        case print: // 立即输出结果
        case quit: //表示退出
        case '(':
        case ')':
        case '{':
        case '}':
        case '+':
        case '-':
        case '*':
        case '/':
        case '%':
        case '=':
            return Token{ch};
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '0': {
            cin.putback(ch);
            double val;
            cin >> val;
            return Token{number, val};
        }
        default:
            if (isalpha(ch)) {
                string s;
                s += ch;
                while (cin.get(ch) && (isalpha(ch) || isdigit(ch)))s += ch;

                cin.putback(ch);
                if (s == declkey)return Token{let};
                return Token{name, s};
            }
            error("bad token");
    }

}


void clean_up_mess() {
    ts.ignore(print);
}


double statement() {
    Token t = ts.get();
    switch (t.kind) {
        case let:
            return declaration();
        default:
            ts.putback(t);
            return expression();
    }

}


bool is_declared(string var) {
    for (const auto &v:var_table) {
        if (v.name == var)return true;
    }
    return false;
}

double define_name(string var, double val) {
    if (is_declared(var)) error(var + "declared twice");
    var_table.push_back(Variable{var, val});
    return val;
}


double declaration() {
    auto t = ts.get();
    if (t.kind != name) {
        error("name expected in declaration");
    }
    string var_name = t.name;

    auto t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of" + var_name);

    double d = expression();
    define_name(var_name, d);
    return d;
}


void calculate() {
    double val = 0;
    while (cin) {
        try {
            cout << prompt;
            Token t = ts.get();

            while (t.kind == print) t = ts.get();
            if (t.kind == quit) return;

            ts.putback(t);
            val = statement();
            cout << result << val << '\n';
        } catch (exception &e) {
            cerr << e.what() << "\n";
            clean_up_mess();
        }

    }
}

int main() {
    try {
        define_name("pi", 3.1415926);
        define_name("e", 2.7182818284);

        calculate();
    } catch (runtime_error &e) {
        cerr << e.what() << "\n";
        cout << "Please enter the character ~ to close the window\n";
        for (char ch; cin >> ch;)
            if (ch == '~')return 1;
        return 1;
    } catch (exception &e) {
        cerr << e.what() << "\n";
        return 1;
    }
    catch (...) {
        cerr << "exception \n";
        return 2;
    }

}

Token get_token() {
    return Token{};
}


double expression() {
    double left = term();
    Token t = ts.get();
    while (true) {
        switch (t.kind) {
            case '+':
                left += term();
                t = ts.get();
                break;
            case '-':
                left -= term();
                t = ts.get();
                break;
            default:
                ts.putback(t);
                return left;
        }
    }
}

double term() {
    double left = primary();
    Token t = ts.get();

    while (true) {
        switch (t.kind) {
            case '*':
                left *= primary();
                t = ts.get();
                break;
            case '/': {
                double d = primary();
                if (d == 0)perror("divide by zero");
                left /= d;
                t = ts.get();
                break;
            }
            case '%': {
                double d = primary();
                if (d == 0)error("divide by zero");
                left = fmod(left, d);
                t = ts.get();
                break;
            }

            default:
                ts.putback(t);
                return left;
        }
    }
}


double primary() {

    Token t = ts.get();

    switch (t.kind) {
        case '(': {
            double d = expression();
            t = ts.get();
            if (t.kind != ')')perror("')' expected");
            return d;
        }
        case '{': {
            double d = expression();
            t = ts.get();
            if (t.kind != '}')perror("'}' expected");
            return d;
        }
        case '-':
            return -primary();
        case '+':
            return primary();
        case number:
            return t.value;
        case name:
            return get_value(t.name);
        default:
            error("primary expected");
    }

}